package com.mapforge.gui.frames;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.event.ChangeListener;

import net.infonode.docking.RootWindow;
import net.infonode.docking.SplitWindow;
import net.infonode.docking.View;
import net.infonode.docking.properties.DockingWindowProperties;
import net.infonode.docking.properties.RootWindowProperties;
import net.infonode.docking.properties.TabWindowProperties;
import net.infonode.docking.util.DockingUtil;
import net.infonode.docking.util.ViewMap;

import com.mapforge.Constants;
import com.mapforge.gui.CutCopyPasteListener;
import com.mapforge.gui.MapEditorMenuBar;
import com.mapforge.gui.StatusLabel;
import com.mapforge.gui.frames.map.NewMapDialogProvider;
import com.mapforge.gui.interfaces.StatusLabelGUIInterface;
import com.mapforge.gui.interfaces.TileMapEditorGUIInterface;
import com.mapforge.gui.interfaces.TileSelectionGUIInterface;
import com.mapforge.gui.map.TileLayerBox;
import com.mapforge.gui.map.TileMapEditorGUIMouseListener;
import com.mapforge.gui.map.TileMapEditorGUIPopupMenuProvider;
import com.mapforge.gui.map.TileMapEditorPanel;
import com.mapforge.gui.map.TileMapPanelScrollPane;
import com.mapforge.gui.map.TileStampActionListener;
import com.mapforge.gui.map.TileStampList;
import com.mapforge.gui.map.TilesetSelectionPanel;
import com.mapforge.gui.tools.MapEditorToolPanel;
import com.mapforge.gui.tools.MapToolButton;
import com.mapforge.gui.tools.TileWriterButton;
import com.mapforge.listener.LoadChangeEvent;
import com.mapforge.listener.MapEditorKeyEventDispatcher;
import com.mapforge.listener.MapToolChangeEvent;
import com.mapforge.listener.TileLayerCreationEvent;
import com.mapforge.listener.TileLayerCreationListener;
import com.mapforge.listener.TileLayerDeletionEvent;
import com.mapforge.listener.TileLayerDeletionListener;
import com.mapforge.listener.TileLayerEditEvent;
import com.mapforge.listener.TileLayerEditListener;
import com.mapforge.listener.TileLayerSelectionListener;
import com.mapforge.listener.TileMapChangeEvent;
import com.mapforge.listener.TileMapChangeListener;
import com.mapforge.listener.TileMapCreationEvent;
import com.mapforge.listener.TileMapCreationListener;
import com.mapforge.listener.TileSelectionListener;
import com.mapforge.listener.TileWriterChangeEvent;
import com.mapforge.listener.TileWriterChangeListener;
import com.mapforge.listener.TilesetEditEvent;
import com.mapforge.listener.TilesetEditListener;
import com.mapforge.map.tilewriter.TileWriter;
import com.mapforge.map.tools.AbstractMapTool;
import com.mapforge.map.tools.MapToolInterface;
import com.mapforge.map.tools.TileClipboard;
import com.mapforge.map.tools.selection.AbstractSelectionTool;
import com.mapforge.map.tools.selection.SelectionMouseAdapter;
import com.mapforge.plugin.PluginManagerDialog;

/**
 * Main frame for the application.
 * 
 * @author William Morrison
 * 
 */
public class MapEditorGUI extends JFrame {
	/**
	 * Default serial version ID, here to remove warnings.
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * Listener which is informed about the GUI's instantiation progress.
	 */
	private ChangeListener listener;

	private Container tileMapScrollPane;

	private JScrollPane tileSelectionScrollPane;

	private JScrollPane tileStampScrollPane;

	/**
	 * Panel where the map is displayed
	 */
	private TileMapEditorGUIInterface tileMapPanel;

	/**
	 * The map editor GUI's menu bar. Provides easy access to the menu items.
	 */
	private MapEditorMenuBar menuBar;

	/**
	 * Panel where the tiles used in editing the map are selected.
	 */
	private TileSelectionGUIInterface tileSelectionPanel;

	/**
	 * Tile stamps for quick selection are displayed in this panel
	 */
	private TileStampList tileStampPanel;

	/**
	 * Tracks what the user is doing and displays for easy reference
	 */
	private StatusLabelGUIInterface statusLabel;

	/**
	 * Holds tools used in editing the map.
	 */
	private MapEditorToolPanel mapEditorToolPanel;

	/**
	 * Displays tile layers the user is manipulating. Tile layers make up the
	 * layers of a tilemap.
	 */
	private TileLayerBox tileLayerToolbar;

	/**
	 * Allows the user to manage plug-ins. (Install, uninstall, ect.)
	 */
	private PluginManagerDialog pluginManagerDialog;

	/**
	 * Holds all the non-trivial dialog frames used in editing (map properties
	 * frame, tileset properties frame etc.)
	 */
	DialogDisplayManager dialogManager;

	/**
	 * Indicates there's been a change to the map.
	 */
	private boolean mapdirty;

	/**
	 * Indicates there's been a change to the tileset.
	 */
	private boolean tilesetdirty;

	/**
	 * Listens to significant events which the user might want to save after
	 * performing.
	 */
	private StateChangeListener stateChangeListener;

	/**
	 * Handles mouse operations on tile map pane
	 */
	private SelectionMouseAdapter selectionMouseAdapter;

	/**
	 * Global key listener
	 */
	private MapEditorKeyEventDispatcher keyDispatcher;

	private String mapTitle;

	public MapEditorGUI() {
		this(null);
	}

	public MapEditorGUI(ChangeListener listener) {
		super(Constants.APP_TITLE);
		Constants.load();
		this.listener = listener;
		tilesetdirty = false;
		mapdirty = false;
		stateChangeListener = new StateChangeListener();
		initMenubar();
		initGUI();
		layoutGUI();
		registerListeners();
		this.fireLoadUpdate("Preparing editor for display", 0);
		pack();
		setLocationRelativeTo(null);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		fireLoadUpdate("Done", 100);
		mapTitle = "";
	}

	/**
	 * 
	 */
	private void initGUI() {

		fireLoadUpdate("Loading plugin manager", 0);
		pluginManagerDialog = new PluginManagerDialog(this);

		fireLoadUpdate("Loading tile layer toolbar", 0);
		tileLayerToolbar = new TileLayerBox();

		fireLoadUpdate("Loading map editor toolbar", 0);
		mapEditorToolPanel = new MapEditorToolPanel(menuBar);

		fireLoadUpdate("Loading status label", 0);
		statusLabel = new StatusLabel();

		fireLoadUpdate("Loading tile map panel", 0);
		try {
			tileMapPanel = new TileMapEditorPanel();
			// tileMapPanel = new TileMapEditorGLPanel();
		} catch (Throwable t) {
			JOptionPane
					.showMessageDialog(this,
							"Unable to use hardware acceleration, fallback to basic rendering.");
			t.printStackTrace();
			tileMapPanel = new TileMapEditorPanel();
		}
		// tileMapScrollPane = new ScrollPane(ScrollPane.SCROLLBARS_AS_NEEDED);
		// tileMapScrollPane.add((Component) tileMapPanel);
		tileMapScrollPane = new TileMapPanelScrollPane(tileMapPanel);

		fireLoadUpdate("Loading tile selection panel", 0);
		tileSelectionPanel = new TilesetSelectionPanel();

		tileSelectionScrollPane = new JScrollPane(
				(Component) tileSelectionPanel);

		fireLoadUpdate("Loading tile stamp panel", 0);
		tileStampPanel = new TileStampList();
		tileStampScrollPane = new JScrollPane(tileStampPanel);

		fireLoadUpdate("Loading dialogs", 0);
		dialogManager = new DialogDisplayManager(this);

		add((Component) statusLabel, BorderLayout.SOUTH);
	}

	/**
	 * Handles organizing the GUI components in the frame. For this to work,
	 * each GUI interface must also be a component.
	 */
	private void layoutGUI() {
		ViewMap viewMap = new ViewMap();
		// views used in application
		View mapView = new View("MapView", null, tileMapScrollPane);
		View tileSelectionView = new View("Tile Selection", null,
				tileSelectionScrollPane);
		View tileStampView = new View("Tile Stamp", null, tileStampScrollPane);
		View statusLabelView = new View("", null, (Component) this
				.getStatusLabel());
		View toolsView = new View("Tools", null, this.getMapEditorToolbar());
		View tileLayerView = new View("Layers", null, this
				.getTileLayerToolbar());

		viewMap.addView(0, mapView);
		RootWindow rootWindow = DockingUtil.createRootWindow(viewMap, false);

		SplitWindow tile_QuickSelection_Split = new SplitWindow(false, .8f,
				tileSelectionView, tileStampView);

		// map tile selection split
		SplitWindow tileSelection_Map_Split = new SplitWindow(true, .3f,
				tile_QuickSelection_Split, mapView);

		// tile/map layers split
		SplitWindow tileSelectionMap_Layers_Split = new SplitWindow(true, .9f,
				tileSelection_Map_Split, tileLayerView);

		// tools tile/map split
		SplitWindow toolsMapSplit = new SplitWindow(false, .1f, toolsView,
				tileSelectionMap_Layers_Split);

		rootWindow.setWindow(new SplitWindow(false, .9f, toolsMapSplit,
				statusLabelView));

		RootWindowProperties rootProp = rootWindow.getRootWindowProperties();

		// hide close button on docking window
		DockingWindowProperties rootDockingProp = rootProp
				.getDockingWindowProperties();
		rootDockingProp.setCloseEnabled(false);

		// hide max/min/close buttons, so only allow docking/undocking
		TabWindowProperties rootWindowTabProp = rootProp
				.getTabWindowProperties();
		rootWindowTabProp.getCloseButtonProperties().setVisible(false);
		rootWindowTabProp.getMaximizeButtonProperties().setVisible(false);
		rootWindowTabProp.getMinimizeButtonProperties().setVisible(false);

		this.add(rootWindow, BorderLayout.CENTER);
	}

	protected void fireLoadUpdate(String data, int prog) {
		if (listener != null) {
			listener.stateChanged(new LoadChangeEvent(this, data, prog));
		}
	}

	private void initMenubar() {
		fireLoadUpdate("Loading menubar", 0);
		menuBar = new MapEditorMenuBar();
		this.setJMenuBar(menuBar);
	}

	/**
	 * This method registers all interested listeners with the appropriate
	 * components.
	 */
	private void registerListeners() {
		fireLoadUpdate("Registering listeners", 0);
		// initialize listeners
		selectionMouseAdapter = new SelectionMouseAdapter();
		keyDispatcher = new MapEditorKeyEventDispatcher();
		CutCopyPasteListener cutCopyPasteListener = new CutCopyPasteListener(
				this.tileMapPanel);

		// register frame listeners with menubar items
		this.menuBar.getAboutItem().addActionListener(
				this.dialogManager.getAboutApplicationDialogProvider());
		this.menuBar.getNewTileLayerItem().addActionListener(
				this.dialogManager.getNewTileLayerDialogProvider());
		this.menuBar.getNewMapItem().addActionListener(
				this.dialogManager.getNewMapDialogProvider());
		this.menuBar.getMapResizeItem().addActionListener(
				this.dialogManager.getResizeMapDialogProvider());
		this.menuBar.getNewTilesetItem().addActionListener(
				this.dialogManager.getNewTilesetDialogProvider());
		this.menuBar.getEditTilesetItem().addActionListener(
				this.dialogManager.getTilesetEditorDialogProvider());

		this.dialogManager.getNewMapDialogProvider().addMapCreationListener(
				cutCopyPasteListener);
		this.tileLayerToolbar.getTileLayerList().addTileLayerSelectionListener(
				cutCopyPasteListener);
		this.tileLayerToolbar.getTileLayerList().addTileLayerDeletionListener(
				cutCopyPasteListener);

		// listens to control key presses, since don't know if it will have
		// focus when control is pressed.
		keyDispatcher.addKeyEventOccurredListener(tileSelectionPanel);

		// ALL GUI interfaces will be components of some type
		Component tileMapPanelComponent = (Component) tileMapPanel;

		// map editor popup provider and mouse listener
		TileMapEditorGUIPopupMenuProvider tileMapEditorPopup = new TileMapEditorGUIPopupMenuProvider(
				tileMapPanel, cutCopyPasteListener);
		tileLayerToolbar.getTileLayerList().addTileLayerSelectionListener(
				tileMapEditorPopup);
		TileMapEditorGUIMouseListener tileMapEditorMouseListener = new TileMapEditorGUIMouseListener(
				tileMapPanel, tileMapEditorPopup);

		tileMapPanelComponent.addMouseListener(selectionMouseAdapter);
		tileMapPanelComponent.addMouseMotionListener(selectionMouseAdapter);
		tileMapPanelComponent.addMouseListener(tileMapEditorMouseListener);
		tileMapPanelComponent
				.addMouseMotionListener(tileMapEditorMouseListener);
		TileClipboard tileClipboard = TileClipboard.getHandle();
		tileClipboard.addRenderListener(tileMapPanel);

		// registering status label listeners
		tileLayerToolbar.getTileLayerList().addTileLayerEditListener(
				statusLabel);
		tileLayerToolbar.getTileLayerList().addTileLayerDeletionListener(
				statusLabel);
		tileLayerToolbar.getTileLayerList().addTileLayerSelectionListener(
				statusLabel);
		tileMapPanelComponent.addMouseMotionListener(statusLabel);
		dialogManager.getNewMapDialogProvider().addMapCreationListener(
				statusLabel);

		dialogManager.getNewMapDialogProvider().addMapCreationListener(
				TileClipboard.getHandle());
		// registering selection mouse adapter for tools
		dialogManager.getNewMapDialogProvider().addMapCreationListener(
				selectionMouseAdapter);

		// registering tile map panel listeners
		dialogManager.getNewTileLayerDialogProvider()
				.addTileLayerCreationListener(tileMapPanel);
		tileLayerToolbar.getTileLayerList().addTileLayerDeletionListener(
				tileMapPanel);
		tileLayerToolbar.getTileLayerList().addTileLayerEditListener(
				tileMapPanel);
		tileLayerToolbar.getTileLayerList()
				.addRenderListener(this.tileMapPanel);

		// register tilelayer change listeners and deletion listeners
		tileLayerToolbar.getTileLayerList().addTileLayerDeletionListener(
				stateChangeListener);
		tileLayerToolbar.getTileLayerList().addTileLayerEditListener(
				stateChangeListener);
		dialogManager.getNewTileLayerDialogProvider()
				.addTileLayerCreationListener(stateChangeListener);
		dialogManager.getNewTileLayerDialogProvider()
				.addTileLayerCreationListener(
						tileLayerToolbar.getTileLayerList());
		dialogManager.getNewMapDialogProvider().addMapCreationListener(
				tileLayerToolbar.getTileLayerList());

		dialogManager.getResizeMapDialogProvider().addRenderListener(
				tileMapPanel);

		// register tile selection panel and tile stamp lists
		this.tileSelectionPanel.addTileSelectionListener(tileStampPanel);
		this.tileStampPanel.addTileSelectionListener(tileSelectionPanel);
		TileStampActionListener tileStampActionListener = new TileStampActionListener();
		tileMapEditorPopup.getCreateTileStampItem().addActionListener(
				tileStampActionListener);
		tileStampActionListener
				.addTileStampCreationListener(this.tileStampPanel);
		this.dialogManager.getNewMapDialogProvider().addMapCreationListener(
				tileStampActionListener);
		this.tileLayerToolbar.getTileLayerList().addTileLayerSelectionListener(
				tileStampActionListener);

		// register editing tools with map panel
		MapToolChangeEvent mapToolSelectedEvent = new MapToolChangeEvent(
				getMapEditorToolbar().getMapToolButtons().get(0),
				getMapEditorToolbar().getMapToolButtons().get(0).getMapTool());
		getMapEditorToolbar().addMapToolChangeListener(statusLabel);
		dialogManager.getNewMapDialogProvider().addMapCreationListener(
				getMapEditorToolbar());

		List<MapToolButton> mapEditorButtons = getMapEditorToolbar()
				.getMapToolButtons();
		List<TileWriterButton> tileWriterButtons = getMapEditorToolbar()
				.getTileWriterButtons();

		// fire fake event to all tools, let them know what writer to use
		TileWriter firstWriter = mapEditorToolPanel.getTileWriterButtons().get(
				0).getTileWriter();
		TileWriterChangeEvent tileWriterInitEvent = new TileWriterChangeEvent(
				this, firstWriter);

		// registering tool buttons with appropriate listeners
		for (MapToolButton toolBtn : mapEditorButtons) {
			toolBtn.addMapToolChangeListener(this.selectionMouseAdapter);
			toolBtn.addMapToolChangeListener(this.tileMapPanel);
			dialogManager.getNewMapDialogProvider().addMapCreationListener(
					toolBtn);
			MapToolInterface mapTool = toolBtn.getMapTool();
			mapTool.addRenderListener(tileMapPanel);
			if (mapTool instanceof AbstractMapTool) {
				AbstractMapTool abstractMapTool = (AbstractMapTool) mapTool;
				abstractMapTool.addUndoableEditListener(tileMapPanel);
			}

			if (mapTool instanceof AbstractSelectionTool) {
				AbstractSelectionTool selectionTool = (AbstractSelectionTool) mapTool;
				keyDispatcher.addKeyEventOccurredListener(selectionTool);
			}
			// check if button implements any of our listeners
			if (toolBtn instanceof TileWriterChangeListener) {
				TileWriterChangeListener listener = (TileWriterChangeListener) toolBtn;
				listener.tileWriterChangePerformed(tileWriterInitEvent);
			}
			if (toolBtn instanceof TileSelectionListener) {
				TileSelectionListener listener = (TileSelectionListener) toolBtn;
				this.tileSelectionPanel.addTileSelectionListener(listener);
				this.tileStampPanel.addTileSelectionListener(listener);
			}
			if (toolBtn instanceof TileLayerSelectionListener) {
				TileLayerSelectionListener listener = (TileLayerSelectionListener) toolBtn;
				tileLayerToolbar.getTileLayerList()
						.addTileLayerSelectionListener(listener);
			}
			if (toolBtn instanceof TileLayerDeletionListener) {
				TileLayerDeletionListener listener = (TileLayerDeletionListener) toolBtn;
				tileLayerToolbar.getTileLayerList()
						.addTileLayerDeletionListener(listener);
			}
		}
		dialogManager.getNewMapDialogProvider().addMapCreationListener(
				mapEditorToolPanel.getZoomToolButton());

		for (int i = 0; i < tileWriterButtons.size(); i++) {
			tileLayerToolbar.getTileLayerList().addTileLayerSelectionListener(
					tileWriterButtons.get(i));
			dialogManager.getNewMapDialogProvider().addTilesetChangeListener(
					tileWriterButtons.get(i));
		}

		statusLabel.mapToolChangePerformed(mapToolSelectedEvent);
		selectionMouseAdapter.mapToolChangePerformed(mapToolSelectedEvent);
		mapEditorToolPanel.getMapToolButtons().get(0).setSelected(true);
		mapEditorToolPanel.getTileWriterButtons().get(0).setSelected(true);

		// New map dialog produces events for tiled map, and tile selection pane
		NewMapDialogProvider newMapDialog = dialogManager
				.getNewMapDialogProvider();
		newMapDialog.addMapCreationListener(tileMapPanel);
		newMapDialog.addMapCreationListener(tileSelectionPanel);

		dialogManager.getNewMapDialogProvider().addMapCreationListener(menuBar);

		this.menuBar.getCutItem().addActionListener(cutCopyPasteListener);
		this.menuBar.getCopyItem().addActionListener(cutCopyPasteListener);
		this.menuBar.getPasteItem().addActionListener(cutCopyPasteListener);
		this.getTileSelectionPanel()
				.addTileSelectionListener(getTileMapPanel());
		this.tileStampPanel.addTileSelectionListener(getTileMapPanel());

		dialogManager.getNewTilesetDialogProvider().addTilesetEditListener(
				tileSelectionPanel);
		dialogManager.getTilesetEditorDialogProvider().addTilesetEditListener(
				tileSelectionPanel);

		dialogManager.getNewMapDialogProvider().addMapCreationListener(
				stateChangeListener);
	}

	/** */
	public void restart() {

	}

	/** */
	private void markTilemapDirty() {
		if (!isTilemapDirty() || !isTilesetDirty()) {
			setTitle("*" + Constants.APP_TITLE + " - " + mapTitle);
		}
		mapdirty = true;
	}

	private void markTilesetDirty() {
		if (!isTilemapDirty() || !isTilesetDirty()) {
			setTitle("*" + Constants.APP_TITLE + " - " + mapTitle);
		}
		tilesetdirty = true;
	}

	public boolean isTilesetDirty() {
		return this.tilesetdirty;
	}

	public boolean isTilemapDirty() {
		return this.mapdirty;
	}

	/** */
	public TileMapEditorGUIInterface getTileMapPanel() {
		return this.tileMapPanel;
	}

	/**
	 * 
	 * @return
	 */
	public TileSelectionGUIInterface getTileSelectionPanel() {
		return this.tileSelectionPanel;
	}

	public DialogDisplayManager getDialogDisplayManager() {
		return this.dialogManager;
	}

	public MapEditorMenuBar getMapEditorMenuBar() {
		return menuBar;
	}

	/** */
	public MapEditorToolPanel getMapEditorToolbar() {
		return this.mapEditorToolPanel;
	}

	/** */
	public StatusLabelGUIInterface getStatusLabel() {
		return this.statusLabel;
	}

	/** */
	public TileLayerBox getTileLayerToolbar() {
		return this.tileLayerToolbar;
	}

	public PluginManagerDialog getPluginManager() {
		return pluginManagerDialog;
	}

	private class StateChangeListener implements TileMapCreationListener,
			TileLayerDeletionListener, TileLayerEditListener,
			TileLayerCreationListener, TileMapChangeListener,
			TilesetEditListener {
		public void tileMapCreationPerformed(
				TileMapCreationEvent mapCreationEvent) {
			tileMapPanel.setTiledMap(mapCreationEvent.getTileMapCreated());
			mapdirty = false;
			tilesetdirty = false;
			mapTitle = mapCreationEvent.getTileMapCreated().getName();
			System.out.println("Map Title = " + mapTitle);
			setTitle(Constants.APP_TITLE + " - " + mapTitle);
		}

		public void tileLayerEditPerformed(TileLayerEditEvent layerChangeEvent) {
			markTilemapDirty();
		}

		public void tileLayerDeletionPerformed(TileLayerDeletionEvent event) {
			markTilesetDirty();
		}

		public void tileLayerCreationPerformed(
				TileLayerCreationEvent tileLayerEvent) {
			markTilesetDirty();
		}

		@Override
		public void tilesetEditHappened(TilesetEditEvent evt) {
			markTilesetDirty();
		}

		@Override
		public void tilemapEditHappend(TileMapChangeEvent event) {
			markTilemapDirty();
		}
	}
}
